Mdland SMART on FHIR OAuth 2.0 Guide
Using OAuth 2.0
Applications must secure and protect the
privacy of patients and their data. To help meet this objective, MDLand
supports using the OAuth 2.0 framework to authenticate and authorize applications.
Why OAuth 2.0?
OAuth 2.0 enables you to develop your
application without having to build a credential management system. Instead of
exposing login credentials to your application, your application and an EHR's
authorization server exchange a series of authorization codes and access
tokens. Your application can access protected patient data stored in an EHR's
database after it obtains authorization from the EHR's authorization server.
Before You Get Started
To use OAuth 2.0 to authorize your application's
access to patient information, some information needs to be shared between the
authorization server and your application:
You'll provide information to us,
including one or more redirect_uris, and MDLand will generate a client_id for you.
Apps can be launched from within an
existing EHR or patient portal session, which is called an EHR launch.
Alternatively, apps can be launched standalone from outside of an existing EHR
session. Apps can also be backend services where no user is launching the app.
EHR launch (SMART on
FHIR):
The app is launched by the EHR calling a launch URL specified in the EHR's
configuration. The EHR launches the launch URL and appends a launch token and
the FHIR server's endpoint URL (ISS parameter) in the query string. The app
exchanges the launch token, along with the client identification parameters to
get an authorization code and eventually the access token.
Standalone launch:
The app launches directly to the authorize endpoint outside of an EHR session
and requests context from the EHR's authorization server.
Backend services:
The app is not authorized by a specific person and likely does not have a user
interface, and therefore calls EHR web services with system-level
authorization.
EHR Launch (SMART
on FHIR)
Contents
How It Works
Step 1: Your Application is
Launched from the Patient Portal or EHR
Your app is launched by the EHR calling
the launch URL which is specified in the EHR's configuration. The EHR sends a
launch token and the FHIR server's endpoint URL (ISS parameter).
Step 2: Your Application
Retrieves the Conformance Statement or SMART Configuration
To determine which authorize and token
endpoints to use in the EHR launch flow, you should make a GET request to the
metadata endpoint which is constructed by taking the iss
provided and appending /metadata.
Metadata Example
Here's an example of what a full metadata
request might look like.
GET https://api-fhir-proxy-2.mdland.net/metadata
HTTP/1.1
Accept:
application/fhir+json
Here's an example of what a
smart-configuration request might look like.
GET
https://api-fhir-proxy-2.mdland.net/.well-known/smart-configuration HTTP/1.1
Accept:
application/json
Here is an example of what the authorize
and token endpoints would look like in the smart-configuration response.
"authorization_endpoint":
" https://api-fhir-proxy-2.mdland.net/authorize",
"token_endpoint": " https://api-fhir-proxy-2.mdland.net/token",
"token_endpoint_auth_methods_supported":
[
"client_secret_post",
"client_secret_basic",
"private_key_jwt"
]
Step 3: Your Application
Requests an Authorization Code
Your application now has a launch token obtained from the initial EHR
launch as well as the authorize endpoint obtained from the metadata query. To
exchange the launch token for an authorization code, your app needs to either
make an HTTP GET or POST request to the authorize endpoint that contains the
parameters below. POST requests are supported for EHR launches in MDLand
version November 2020 and later, and are not currently
supported for standalone launches.
For POST requests, the parameters should
be in the body of the request and should be sent as Content-Type
"application/x-www-form-urlencoded". For
GET requests, the parameters should be appended in the querystring
of the request. It is MDLand's recommendation to use
HTTP POST for EHR launches to overcome any size limits on the request URL.
In either case, the application should
redirect the User-Agent (the user's browser) to the authorization server by
either submitting an HTML Form to the authorization server (for HTTP POST) or
by redirecting to a specified URL (for HTTP GET). The request should contain
the following parameters.
Step 4: EHR's Authorization
Server Reviews the Request
The EHR's authorization server reviews
the request from your application. If approved, the authorization server
redirects the browser to the redirect URL supplied in the initial request and
appends the following querystring parameter.
Step 5: Your Application
Exchanges the Authorization Code for an Access Token
After receiving the authorization code,
your application trades the code for a JSON object containing an access token
and contextual information by sending an HTTP POST to the token endpoint using
a Content-Type header with value of "application/x-www-form-urlencoded". For more information, see RFC 6749 section 4.1.3.
Access Token Request: If You Are Not
Using a Client Secret
The following parameters are required in
the POST body:
The authorization server responds to the
HTTP POST request with a JSON object that includes an access token. The
response contains the following fields:
At this point, authorization is complete and the web application can access the protected
patient data it requests using FHIR APIs.
Access Token Request: If You Are Using a
Client Secret
Refresh tokens are not typically needed
for embedded (SMART on FHIR) launches because users are not required to log in
to MDLand during the SMART on FHIR launch process, and the access token
obtained from the launch process is typically valid for longer than the user
needs to use the app.
Consult the Standalone Launch:
Access Token Request for Refresh Token Apps for details on how to obtain
an access token if your app uses refresh tokens.
OpenID Connect id_tokens
A decoded OpenID Connect id_token JWT will have these headers:
Header |
Description |
alg |
The JWT authentication algorithm. |
typ |
This is always set to JWT. |
kid |
The base64 encoded SHA-256 hash of
the public key. |
A decoded OpenID Connect id_token JWT will have this payload:
Claim |
Description |
Remarks |
iss |
Issuer of the JWT. This is set to
the token endpoint that should be used by the client. |
|
sub |
STU3+ FHIR ID for the resource
representing the user launching the app. |
|
fhirUser |
Absolute reference to the FHIR
resource representing the user launching the app. See the HL7 documentation for more details. |
|
aud |
Audiences that the ID token is
intended for. This will be set to the client ID for the application that was
just authorized during the SMART on FHIR launch. |
|
iat |
Time integer for when the JWT was
created, expressed in seconds since the "Epoch" (1970-01-01T00:00:00Z
UTC). |
|
exp |
Expiration time integer for this
authentication JWT, expressed in seconds since the "Epoch"
(1970-01-01T00:00:00Z UTC). |
This is set to the current time
plus 5 minutes. |
Step 6: Your Application Uses
FHIR APIs to Access Patient Data
With a valid access token, your
application can now access protected patient data from the EHR database using
FHIR APIs. Queries must contain an Authorization header that includes the
access token presented as a Bearer token.
Here's an example of what a valid query
looks like:
GET
https://api-fhir-proxy-2.mdland.net/Patient/47 HTTP/1.1
Authorization:
Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIzZnhzNXY3UF9XaXZtbmJ3Z1RDVVpCUXJ3VXdkaFE3V09ad19lT05DdTJRIn0.eyJleHAiOjE2NjkzMDIwNDgsImlhdCI6MTY2OTMwMTc0OCwiYXV0aF90aW1lIjoxNjY5MzAxNzQ4LCJqdGkiOiIwMDRmYTg1Ni1hM2EwLTQxMDctYWQ1Zi05MTkzNzdmZmE4MmEiLCJpc3MiOiJodHRwczovL2FwaS1hdXRoLTIubWRsYW5kLm5ldC9yZWFsbXMvRzEwIiwic3ViIjoiYTIwMzBlMDMtMzBkZi00MDkyLThkYjItMGFmOGQzM2QyYzM4IiwidHlwIjoiQmVhcmVyIiwiYXpwIjoidGVzdF9hdXRoX3Byb3h5Iiwic2Vzc2lvbl9zdGF0ZSI6IjA5ZWM3MThkLWQ3MWEtNGVlOS05MjM5LTMyZjI1ZjFmOGMyYyIsImFjciI6IjEiLCJzY29wZSI6InBhdGllbnQvTWVkaWNhdGlvblJlcXVlc3QucmVhZCBwYXRpZW50L0RvY3VtZW50UmVmZXJlbmNlLnJlYWQgcGF0aWVudC9Pcmdhbml6YXRpb24ucmVhZCBwYXRpZW50L0NhcmVUZWFtLnJlYWQgcGF0aWVudC9JbW11bml6YXRpb24ucmVhZCBwYXRpZW50L0NvbmRpdGlvbi5yZWFkIGVtYWlsIHBhdGllbnQvR29hbC5yZWFkIHBhdGllbnQvRW5jb3VudGVyLnJlYWQgcGF0aWVudC9PYnNlcnZhdGlvbi5yZWFkIHBhdGllbnQvTG9jYXRpb24ucmVhZCBsYXVuY2gvcGF0aWVudCBwYXRpZW50L0FsbGVyZ3lJbnRvbGVyYW5jZS5yZWFkIHBhdGllbnQvUHJvdmVuYW5jZS5yZWFkIHBhdGllbnQvUGF0aWVudC5yZWFkIHBhdGllbnQvRGlhZ25vc3RpY1JlcG9ydC5yZWFkIHBhdGllbnQvUHJvY2VkdXJlLnJlYWQgcGF0aWVudC9EZXZpY2UucmVhZCBwYXRpZW50L01lZGljYXRpb24ucmVhZCBvcGVuaWQgcGF0aWVudC9QcmFjdGl0aW9uZXJSb2xlLnJlYWQgcGF0aWVudC9DYXJlUGxhbi5yZWFkIGZoaXJVc2VyIG9mZmxpbmVfYWNjZXNzIHBhdGllbnQvUHJhY3RpdGlvbmVyLnJlYWQiLCJzaWQiOiIwOWVjNzE4ZC1kNzFhLTRlZTktOTIzOS0zMmYyNWYxZjhjMmMiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInBhdGllbnQiOiI0NyIsImZoaXJVc2VyIjoiaHR0cHM6Ly9oYXBpLWZoaXIubWRsYW5kLm5ldC9maGlyL1BhdGllbnQvNDcifQ.C-Yo3IxTNI5HCqfcX7xUwe11KBgA_unvIxBz3-y7NnnYPb6UewEh3WXSA1CEAL6m9s4DcQxc8f1zQ3NpJh62Gc6F7_bJ0ERe0lYqxK7FL_ANVPK1ge8adz6W3r_RWaOxkOuUTdfEfuDN9_IORsWmHZrXbIB8hU_CZBbY5eK5ssLRym7SYglSmMO_jYfms6Alm2V1GCNULmwLcPmYEle5Wx9JpBMtdsLUkghRLxRA4eZ4YLD6UdmvrCY_zToN8ArHXXucjqyoeJSTK6DcDsSrRPQsXCQBAMLX4FDnNJ_3iHet0Fmhuvq0EXZPuRzgiCo0VFuSkwZGasRqFnE_JTrGtg
Step 7: Use a Refresh Token to
Obtain a New Access Token
Refresh tokens are not typically needed
for embedded (SMART on FHIR) launches because users are not required to log in
to MDLand during the SMART on FHIR launch process, and the access token obtained
from the launch process is typically valid for longer than the user needs to
use the app.
Consult the Standalone Launch: Use a
Refresh Token to Obtain a New Access Token for details on how to use a
refresh token to get a new an access token if your app uses refresh tokens.
Standalone Launch
Contents
How It Works
Step 1: Your Application
Requests an Authorization Code
Your application would like to
authenticate the user using the OAuth 2.0 workflow. To initiate this process,
your app needs to link (using HTTP GET) to the authorize endpoint and append
the following querystring parameters:
Step 2: EHR's Authorization
Server Authenticates the User and Authorizes Access
The EHR's authorization server reviews
the request from your application, authenticates the user (sample
credentials found here), and authorizes access If approved, the authorization
server redirects the browser to the redirect URL supplied in the initial
request and appends the following querystring
parameter.
Step 3: Your Application
Exchanges the Authorization Code for an Access Token
After receiving the authorization code,
your application trades the code for a JSON object containing an access token
and contextual information by sending an HTTP POST to the token endpoint using
a Content-Type header with value of "application/x-www-form-urlencoded". For more information, see RFC 6749 section 4.1.3.
Access Token Request: If You Are Not
Using a Client Secret
The following parameters are required in
the POST body:
The authorization server responds to the
HTTP POST request with a JSON object that includes an access token. The
response contains the following fields:
At this point, authorization is complete and the web application can access the protected
patient data it requested using FHIR APIs.
Access Token Request: If You Are Using a
Client Secret:
After receiving the authorization code, your
application trades the code for a JSON object containing an access token and
contextual information by sending an HTTP POST to the token endpoint using a
Content-Type header with value of "application/x-www-form-urlencoded". For more information, see RFC 6749 section 4.1.3.
The following parameters are required in
the POST body:
Note: The client_id parameter
is not passed in the the POST body if you use client
secret authentication, which is different from the access token request for
apps that do not use refresh tokens. You will instead pass an Authorization HTTP header
with client_id and client_secret URL
encoded and passed as a username and password. Conceptually the Authorization HTTP header will
have this value: base64(client_id:client_secret).
For example, using the following client_id and client_secret:
client_id:
d45049c3-3441-40ef-ab4d-b9cd86a17225
URL encoded client_id:
d45049c3-3441-40ef-ab4d-b9cd86a17225 Note: base64 encoding MDLand's client IDs will have no effect
client_secret:
this-is-the-secret-2/7
URL encoded client_secret: this-is-the-secret-2%2F7
Would result in this Authorization header:
Authorization:
Basic base64Encode{d45049c3-3441-40ef-ab4d-b9cd86a17225:this-is-the-secret-2%2F7}
or
Authorization:
Basic
ZDQ1MDQ5YzMtMzQ0MS00MGVmLWFiNGQtYjljZDg2YTE3MjI1OnRoaXMtaXMtdGhlLXNlY3JldC0yJTJGNw==
Here's an example of what a valid HTTP
POST might look like:
POST
https://api-fhir-proxy-2.mdland.net/token HTTP/1.1
Content-Type:
application/x-www-form-urlencoded
Authorization:
Basic
ZDQ1MDQ5YzMtMzQ0MS00MGVmLWFiNGQtYjljZDg2YTE3MjI1OnRoaXMtaXMtdGhlLXNlY3JldC0yJTJGNw==
grant_type=authorization_code&code=yfNg-rSc1t5O2p6jVAZLyY00uOOte5KM1y3YUxqsJQnBKEMNsYqOPTyVqcCH3YXaPkLztO9Rvf7bhLqQTwALHcHN6raxpTbR1eVgV2QyLA_4K0HrJO92et3qRXiXPkj7&redirect_uri=https%3A%2F%2Ffhir.epic.com%2Ftest%2Fsmart
The authorization server responds to the
HTTP POST request with a JSON object that includes an access token and a
refresh token. The response contains the following fields:
Note that you can pass additional
parameters if needed based on the integration configuration. Here's an example
of what a JSON object including an access token and refres
token might look like:
{
"access_token":
"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIzZnhzNXY3UF9XaXZtbmJ3Z1RDVVpCUXJ3VXdkaFE3V09ad19lT05DdTJRIn0.eyJleHAiOjE2NjkzMDIwNDgsImlhdCI6MTY2OTMwMTc0OCwiYXV0aF90aW1lIjoxNjY5MzAxNzQ4LCJqdGkiOiIwMDRmYTg1Ni1hM2EwLTQxMDctYWQ1Zi05MTkzNzdmZmE4MmEiLCJpc3MiOiJodHRwczovL2FwaS1hdXRoLTIubWRsYW5kLm5ldC9yZWFsbXMvRzEwIiwic3ViIjoiYTIwMzBlMDMtMzBkZi00MDkyLThkYjItMGFmOGQzM2QyYzM4IiwidHlwIjoiQmVhcmVyIiwiYXpwIjoidGVzdF9hdXRoX3Byb3h5Iiwic2Vzc2lvbl9zdGF0ZSI6IjA5ZWM3MThkLWQ3MWEtNGVlOS05MjM5LTMyZjI1ZjFmOGMyYyIsImFjciI6IjEiLCJzY29wZSI6InBhdGllbnQvTWVkaWNhdGlvblJlcXVlc3QucmVhZCBwYXRpZW50L0RvY3VtZW50UmVmZXJlbmNlLnJlYWQgcGF0aWVudC9Pcmdhbml6YXRpb24ucmVhZCBwYXRpZW50L0NhcmVUZWFtLnJlYWQgcGF0aWVudC9JbW11bml6YXRpb24ucmVhZCBwYXRpZW50L0NvbmRpdGlvbi5yZWFkIGVtYWlsIHBhdGllbnQvR29hbC5yZWFkIHBhdGllbnQvRW5jb3VudGVyLnJlYWQgcGF0aWVudC9PYnNlcnZhdGlvbi5yZWFkIHBhdGllbnQvTG9jYXRpb24ucmVhZCBsYXVuY2gvcGF0aWVudCBwYXRpZW50L0FsbGVyZ3lJbnRvbGVyYW5jZS5yZWFkIHBhdGllbnQvUHJvdmVuYW5jZS5yZWFkIHBhdGllbnQvUGF0aWVudC5yZWFkIHBhdGllbnQvRGlhZ25vc3RpY1JlcG9ydC5yZWFkIHBhdGllbnQvUHJvY2VkdXJlLnJlYWQgcGF0aWVudC9EZXZpY2UucmVhZCBwYXRpZW50L01lZGljYXRpb24ucmVhZCBvcGVuaWQgcGF0aWVudC9QcmFjdGl0aW9uZXJSb2xlLnJlYWQgcGF0aWVudC9DYXJlUGxhbi5yZWFkIGZoaXJVc2VyIG9mZmxpbmVfYWNjZXNzIHBhdGllbnQvUHJhY3RpdGlvbmVyLnJlYWQiLCJzaWQiOiIwOWVjNzE4ZC1kNzFhLTRlZTktOTIzOS0zMmYyNWYxZjhjMmMiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInBhdGllbnQiOiI0NyIsImZoaXJVc2VyIjoiaHR0cHM6Ly9oYXBpLWZoaXIubWRsYW5kLm5ldC9maGlyL1BhdGllbnQvNDcifQ.C-Yo3IxTNI5HCqfcX7xUwe11KBgA_unvIxBz3-y7NnnYPb6UewEh3WXSA1CEAL6m9s4DcQxc8f1zQ3NpJh62Gc6F7_bJ0ERe0lYqxK7FL_ANVPK1ge8adz6W3r_RWaOxkOuUTdfEfuDN9_IORsWmHZrXbIB8hU_CZBbY5eK5ssLRym7SYglSmMO_jYfms6Alm2V1GCNULmwLcPmYEle5Wx9JpBMtdsLUkghRLxRA4eZ4YLD6UdmvrCY_zToN8ArHXXucjqyoeJSTK6DcDsSrRPQsXCQBAMLX4FDnNJ_3iHet0Fmhuvq0EXZPuRzgiCo0VFuSkwZGasRqFnE_JTrGtg",
"expires_in": 300,
"refresh_expires_in": 0,
"refresh_token":
"eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI1ZjJiNmU1Zi02MGI3LTQ3M2QtOThlYS05OTMzMWZlNWM2ZWYifQ.eyJpYXQiOjE2NjkzMDE3NDgsImp0aSI6ImIzYmZmMDk2LTFjZWUtNDc3Yi04MjhiLTM0YjZiZjMzNTZhNyIsImlzcyI6Imh0dHBzOi8vYXBpLWF1dGgtMi5tZGxhbmQubmV0L3JlYWxtcy9HMTAiLCJhdWQiOiJodHRwczovL2FwaS1hdXRoLTIubWRsYW5kLm5ldC9yZWFsbXMvRzEwIiwic3ViIjoiYTIwMzBlMDMtMzBkZi00MDkyLThkYjItMGFmOGQzM2QyYzM4IiwidHlwIjoiT2ZmbGluZSIsImF6cCI6InRlc3RfYXV0aF9wcm94eSIsInNlc3Npb25fc3RhdGUiOiIwOWVjNzE4ZC1kNzFhLTRlZTktOTIzOS0zMmYyNWYxZjhjMmMiLCJzY29wZSI6InBhdGllbnQvTWVkaWNhdGlvblJlcXVlc3QucmVhZCBwYXRpZW50L0RvY3VtZW50UmVmZXJlbmNlLnJlYWQgcGF0aWVudC9Pcmdhbml6YXRpb24ucmVhZCBwYXRpZW50L0NhcmVUZWFtLnJlYWQgcGF0aWVudC9JbW11bml6YXRpb24ucmVhZCBwYXRpZW50L0NvbmRpdGlvbi5yZWFkIGVtYWlsIHBhdGllbnQvR29hbC5yZWFkIHBhdGllbnQvRW5jb3VudGVyLnJlYWQgcGF0aWVudC9PYnNlcnZhdGlvbi5yZWFkIHBhdGllbnQvTG9jYXRpb24ucmVhZCBsYXVuY2gvcGF0aWVudCBwYXRpZW50L0FsbGVyZ3lJbnRvbGVyYW5jZS5yZWFkIHBhdGllbnQvUHJvdmVuYW5jZS5yZWFkIHBhdGllbnQvUGF0aWVudC5yZWFkIHBhdGllbnQvRGlhZ25vc3RpY1JlcG9ydC5yZWFkIHBhdGllbnQvUHJvY2VkdXJlLnJlYWQgcGF0aWVudC9EZXZpY2UucmVhZCBwYXRpZW50L01lZGljYXRpb24ucmVhZCBvcGVuaWQgcGF0aWVudC9QcmFjdGl0aW9uZXJSb2xlLnJlYWQgcGF0aWVudC9DYXJlUGxhbi5yZWFkIGZoaXJVc2VyIG9mZmxpbmVfYWNjZXNzIHBhdGllbnQvUHJhY3RpdGlvbmVyLnJlYWQiLCJzaWQiOiIwOWVjNzE4ZC1kNzFhLTRlZTktOTIzOS0zMmYyNWYxZjhjMmMifQ.oS3dx92NgzV_IjnkvZm4tKhvcbtsZqniEZ0xrBcrgvc",
"token_type": "Bearer",
"id_token":
"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIzZnhzNXY3UF9XaXZtbmJ3Z1RDVVpCUXJ3VXdkaFE3V09ad19lT05DdTJRIn0.eyJleHAiOjE2NjkzMDIwNDgsImlhdCI6MTY2OTMwMTc0OCwiYXV0aF90aW1lIjoxNjY5MzAxNzQ4LCJqdGkiOiJkYmRjMzVkMy1mZGJlLTQzZmYtODM0Yi1jOTMyMWYyMTM2ZTgiLCJpc3MiOiJodHRwczovL2FwaS1hdXRoLTIubWRsYW5kLm5ldC9yZWFsbXMvRzEwIiwiYXVkIjoidGVzdF9hdXRoX3Byb3h5Iiwic3ViIjoiYTIwMzBlMDMtMzBkZi00MDkyLThkYjItMGFmOGQzM2QyYzM4IiwidHlwIjoiSUQiLCJhenAiOiJ0ZXN0X2F1dGhfcHJveHkiLCJzZXNzaW9uX3N0YXRlIjoiMDllYzcxOGQtZDcxYS00ZWU5LTkyMzktMzJmMjVmMWY4YzJjIiwiYXRfaGFzaCI6IlB6VVdXc2xEalRXdlFjY1VLa1g2TXciLCJhY3IiOiIxIiwic2lkIjoiMDllYzcxOGQtZDcxYS00ZWU5LTkyMzktMzJmMjVmMWY4YzJjIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwYXRpZW50IjoiNDciLCJmaGlyVXNlciI6Imh0dHBzOi8vaGFwaS1maGlyLm1kbGFuZC5uZXQvZmhpci9QYXRpZW50LzQ3In0.NpEaxnfgzcknOmP3J9nc9-1fp1slF0PzJSXz9015_pmdqlcrLLArWAN8-Q_2biM02QSHl7MEjbaaPUz63VyQIoQrsbtIvHwQWOnFHJkjyv_u6CiG4rwMHUHL7Sb7zW5y7c-6fiiUb4b73OK6ocGeLS9cvKatJPMc4awHo2_b6WoApnZu7aDZlwxrAqnfUS1XxT6Zb9_ZPaw6c7fEHP5Gu7-0avvW3GhdnELwJVj1aJVjYviIqMmo_lAVjhwuZwBygXbDRYK0WjiwdGBXiQQnKfuKrnSlhlTbzTURofC9wF__BvBN7CcKtc5cyWaN9QM0zeiad5ULrW2DJSd4jy5f7g",
"not-before-policy":
1669044176,
"session_state":
"09ec718d-d71a-4ee9-9239-32f25f1f8c2c",
"scope": "patient/MedicationRequest.read patient/DocumentReference.read
patient/Organization.read patient/CareTeam.read
patient/Immunization.read patient/Condition.read
email patient/Goal.read patient/Encounter.read
patient/Observation.read patient/Location.read
launch/patient patient/AllergyIntolerance.read
patient/Provenance.read patient/Patient.read
patient/DiagnosticReport.read patient/Procedure.read patient/Device.read
patient/Medication.read openid
patient/PractitionerRole.read patient/CarePlan.read fhirUser offline_access patient/Practitioner.read",
"smart_style_url":
"https://api-fhir-proxy-2.mdland.net/smart_style",
"need_patient_banner": false,
"patient": "47"
}
At this point, authorization is complete and the web application can access the protected
patient data it requested using FHIR APIs.
Step 4: Your Application Uses
FHIR APIs to Access Patient Data
With a valid access token, your
application can now access protected patient data from the EHR database using
FHIR APIs. Queries must contain an Authorization header that includes the
access token presented as a Bearer token.
Step 5: Use a Refresh Token to
Obtain a New Access Token
If your app uses refresh tokens (i.e. it can securely store credentials), then you can use a
refresh token to request a new access token when the current access token
expires (determined by the expires_in field from the authorization
response from step 3).
Your application trades the refresh_token for
a JSON object containing a new access token and contextual information by
sending an HTTP POST to the token endpoint using a Content-Type HTTP header with
value of "application/x-www-form-urlencoded".
For more information, see RFC 6749 section 4.1.3.
The following parameters are required in
the POST body:
An Authorization header using HTTP Basic
Authentication is required, where the username is the URL encoded client_id and
the password is the URL encoded client_secret.
For example, using the following client_id and client_secret:
client_id:
d45049c3-3441-40ef-ab4d-b9cd86a17225
URL encoded client_id:
d45049c3-3441-40ef-ab4d-b9cd86a17225 Note: base64 encoding MDLand's client IDs will have no effect
client_secret:
this-is-the-secret-2/7
URL encoded client_secret: this-is-the-secret-2%2F7
Would result in this Authorization header:
Authorization:
Basic base64Encode{d45049c3-3441-40ef-ab4d-b9cd86a17225:this-is-the-secret-2%2F7}
or
Authorization:
Basic ZDQ1MDQ5YzMtMzQ0MS00MGVmLWFiNGQtYjljZDg2YTE3MjI1OnRoaXMtaXMtdGhlLXNlY3JldC0yJTJGNw==
Here's an example of what a valid HTTP
POST might look like:
POST
https://api-fhir-proxy-2.mdland.net/token HTTP/1.1
Authorization:
Basic ZDQ1MDQ5YzMtMzQ0MS00MGVmLWFiNGQtYjljZDg2YTE3MjI1OnRoaXMtaXMtdGhlLXNlY3JldC0yJTJGNw==
Content-Type:
application/x-www-form-urlencoded
grant_type=refresh_token&refresh_token=j12xcniournlsdf234bgsd
The authorization server responds to the
HTTP POST request with a JSON object that includes the new access token. The
response contains the following fields:
An example response to the previous
request may look like the following:
{
"access_token":
"57CjhZEdiTcCh1nqIwQUw5rODOLP3bSTnMEGNYbBerSeNn8hIUm6Mlc5ruCTfQawjRAoR8sYr8S_7vNdnJfgRKfD6s8mOqPnvJ8vZOHjEvy7l3Ra9frDaEAUBbN-j86k",
"token_type":
"bearer",
"expires_in": 3240,
"scope":
"Patient.read Patient.search
",
"refresh_token": "b72foiua9asdhnkjanvm"
}
SMART Backend
Services (Backend OAuth 2.0)
Contents
Overview
Backend apps (i.e.
apps without direct end user or patient interaction) can also use OAuth 2.0
authentication through the client_credentials OAuth
2.0 grant type. MDLand's OAuth 2.0 implementation for
backend services follows the SMART Backend Services:
Authorization Guide, though it currently differs from that
profile in some respects. Application vendors pre-register a public key for a
given MDLand community member on the MDLand on FHIR website and then use the
corresponding private key to sign a JSON Web Token (JWT) which
is presented to the authorization server to obtain an access token.
Building a Backend OAuth 2.0 App
To use the client_credentials
OAuth 2.0 grant type to authorize your backend application's access to patient
information, two pieces of information need to be shared between the
authorization server and your application:
Using a JWT to Obtain an Access Token for
a Backend Service
You will generate a one-time use JSON Web
Token (JWT) to authenticate your app to the authorization server and obtain an
access token that can be used to authenticate your app's web service calls.
There are several libraries for creating JWTs. See jwt.io for
some examples.
Step 1: Creating the JWT
See the Creating a Public Private
Key Pair for JWT Signature and the Creating a JWT sections for
details on how to create a signed JWT.
Step 2: POSTing
the JWT to Token Endpoint to Obtain Access Token
Your application makes a HTTP POST
request to the authorization server's OAuth 2.0 token endpoint to obtain access
token. The following form-urlencoded parameters are
required in the POST body:
Here is an example request:
POST https://api-fhir-proxy-2.mdland.net/token
HTTP/1.1
Content-Type:
application/x-www-form-urlencoded
grant_type=client_credentials&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&client_assertion=eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkNDUwNDljMy0zNDQxLTQwZWYtYWI0ZC1iOWNkODZhMTcyMjUiLCJzdWIiOiJkNDUwNDljMy0zNDQxLTQwZWYtYWI0ZC1iOWNkODZhMTcyMjUiLCJhdWQiOiJodHRwczovL2ZoaXIuZXBpYy5jb20vaW50ZXJjb25uZWN0LWZoaXItb2F1dGgvb2F1dGgyL3Rva2VuIiwianRpIjoiZjllYWFmYmEtMmU0OS0xMWVhLTg4ODAtNWNlMGM1YWVlNjc5IiwiZXhwIjoxNTgzNTI0NDAyLCJuYmYiOjE1ODM1MjQxMDIsImlhdCI6MTU4MzUyNDEwMn0.dztrzHo9RRwNRaB32QxYLaa9CcIMoOePRCbpqsRKgyJmBOGb9acnEZARaCzRDGQrXccAQ9-syuxz5QRMHda0S3VbqM2KX0VRh9GfqG3WJBizp11Lzvc2qiUPr9i9CqjtqiwAbkME40tioIJMC6DKvxxjuS-St5pZbSHR-kjn3ex2iwUJgPbCfv8cJmt19dHctixFR6OG-YB6lFXXpNP8XnL7g85yLOYoQcwofN0k8qK8h4uh8axTPC21fv21mCt50gx59XgKsszysZnMDt8OG_G4gjk_8JnGHwOVkJhqj5oeg_GdmBhQ4UPuxt3YvCOTW9S2vMikNUnxrhdVvn2GVg
And here is an example response body
assuming the authorization server approves the request:
{
"access_token":
"i82fGhXNxmidCt0OdjYttm2x0cOKU1ZbN6Y_-zBvt2kw3xn-MY3gY4lOXPee6iKPw3JncYBT1Y-kdPpBYl-lsmUlA4x5dUVC1qbjEi1OHfe_Oa-VRUAeabnMLjYgKI7b",
"token_type":
"bearer",
"expires_in": 3600,
"scope":
"Patient.read Patient.search"
}
JSON Web Tokens
(JWTs)
Contents
Background
JSON Web Tokens (JWTs) can
be used for some OAuth 2.0 workflows. JWTs are required to be used by the
client credentials flow used by backend services, and can be used, as an
alternative to client secrets, for apps that use refresh tokens. You will
generate a one-time use JWT to authenticate your app to the authorization
server and obtain an access token that can be used to authenticate your app's
web service calls, and potentially a refresh token that can be used to get
another access token without a user needing to authorize the request. There are
several libraries for creating JWTs. See jwt.io for
some examples.
Creating a JWT
The JWT should have these headers:
Header |
Description |
alg |
The JWT authentication algorithm. |
typ |
This should always be set to JWT. |
kid |
For apps using JSON Web Key
Sets (including dynamically registed clients),
set this value to the kid of the target public key
from your key set |
jku |
For apps using JSON Web Key Set
URLs, optionally set this value to the URL you registered on your application |
The JWT header should be formatted as
follows:
{
"alg": "RS384",
"typ": "JWT"
}
The JWT should have these claims in the
payload:
Claim |
Description |
Remarks |
iss |
Issuer of the JWT. This is the
app's client_id, as determined during
registration on the MDLand on FHIR website, or the client_id returned during
a dynamic registration |
This is the same as the value for
the sub claim. |
sub |
Issuer of the JWT. This is the
app's client_id, as determined during
registration on the MDLand on FHIR website, or the client_id returned during
a dynamic registration |
This is the same as the value for
the iss claim. |
aud |
The FHIR authorization server's
token endpoint URL. This is the same URL to which this authentication JWT
will be posted. See below for an example POST. |
It's possible that MDLand
community member systems will route web service traffic through a proxy
server, in which case the URL the JWT is posted to is not known to the
authorization server, and the JWT will be rejected. For such cases, MDLand
community member administrators can add additional audience URLs to the allowlist, in addition to the FHIR server token URL if
needed. |
jti |
A unique identifier for the JWT. |
The jti must be no longer than 151
characters and cannot be reused during the JWT's validity period, i.e. before the exp time
is reached. |
exp |
Expiration time integer for this
authentication JWT, expressed in seconds since the "Epoch"
(1970-01-01T00:00:00Z UTC). |
The exp value
must be in the future, and can be no more than 5
minutes in the future at the time the access token request is received. |
nbf |
Time integer before which the JWT
must not be accepted for processing, expressed in seconds since the
"Epoch" (1970-01-01T00:00:00Z UTC). |
The nbf value cannot be in the
future, cannot be more recent than the exp value,
and the exp - nbf difference cannot be greater
than 5 minutes. |
iat |
Time integer for when the JWT was
created, expressed in seconds since the "Epoch"
(1970-01-01T00:00:00Z UTC). |
The iat value cannot be in the
future, and the exp - iat difference cannot be greater
than 5 minutes. |
Here's an example JWT payload:
{
"iss":
"d45049c3-3441-40ef-ab4d-b9cd86a17225",
"sub":
"d45049c3-3441-40ef-ab4d-b9cd86a17225",
"aud":
"https://api-fhir-proxy-2.mdland.net/token",
"jti":
"f9eaafba-2e49-11ea-8880-5ce0c5aee679",
"exp":
1583524402,
"nbf": 1583524102,
"iat": 1583524102
}
The header and payload are then base64
URL encoded, combined with a period separating them, and cryptographically
signed using the private key to generate a signature. Conceptually:
signature
= RSA-SHA384(base64urlEncoding(header) + '.' + base64urlEncoding(payload), privatekey)
The full JWT is constructed by combining
the header, body and signature as follows:
base64urlEncoding(header)
+ '.' + base64urlEncoding(payload) + '.' + base64urlEncoding(signature)
JSON Web Key Sets
Applications using JSON Web Token (JWT)
authentication can provide their public keys to MDLand as a JSON Web Key (JWK) Set.
Each key in the set should be of type RSA and have the kty, n , e and kid fields present:
{
"keys": [
{ "kty":"RSA",
"e": "AQAB", "kid":
"d1fc1715-3299-43ec-b5de-f943803314c2", "n":
"uPkpNCkqbbismKNwKguhL0P4Q40sbyUkUFcmDAACqBntWerfjv9VzC3cAQjwh3NpJyRKf7JvwxrbELvPRMRsXefuEpafHfNAwj3acTE8xDRSXcwzQwd7YIHmyXzwHDfmOSYW7baAJt-g_FiqCV0809M9ePkTwNvjpb6tlJu6AvrNHq8rVn1cwvZLIG6KLCTY-EHxNzsBblJYrZ5YgR9sfBDo7R-YjE8c761PSrBmUM4CAQHtQu_w2qa7QVaowFwcOkeqlSxZcqqj8evsmRfqJWoCgAAYeRIsgKClZaY5KC1sYHIlLs2cp2QXgi7rb5yLUVBwpSWM4AWJ_J5ziGZBSQYB4sWWn8bjc5-k1JpUnf88-UVZv9vrrkMJjNam32Z6FNm4g49gCVu_TH5M83_pkrsNWwCu1JquY9Z-eVNCsU_AWPgHeVZyXT6giHXZv_ogMWSh-3opMt9dzPwYseG9gTPXqDeKRNWFEm46X1zpcjh-sD-8WcAlgaEES6ys_O8Z"
},
{ "kty":"RSA",
"e":"AQAB","kid":"1248110c-afbd-484c-b75b-b30200ffcf05","n":"zYlmlLzhQNpRq263CoShHgyJCrNLm6oFrf0FTQ0pwYIyaTFZirW-G6wDyVwtaQA5Kjth0NjglQIYfPR9rPBGmX3CSVtmZCLEhFeMMp_W3SoZFZJJypP4x2AbG7TqPvkbpuYWHT15Lvp9amgGqdNQ-UyPZS8WMp1NloDw4R9Nomo8yEk8OcgmG1ORqrmFLam3AHPvZZH2wp88gE9DWOAoOwOpDP32QSo1ii0Wrw2gV_UwsVuAjhkFK3asOCHoqlRhm_c49d1OgB7QvrnP-S5-Wa1JsQ5lKa52G7UVkyNPA1pFw7iW7PHXCgoe8inSmnrqkzmtmDjpOnqpuGU39I6byQ"}
]
}
Key Identifier Requirements
MDLand recommends your application
provide the kid field
in JWK Sets and in your JWT Headers, since MDLand can use the identifier
to find your intended key. MDLand does not require your key identifiers to
be thumbprints,
and will accept any value that is unique within your key set.
Hosting a JWK Set URL
Apps using JWT authentication need use
a JSON Web Key Set URL to provide public keys to MDLand for JWT
verification. JWK Set URLs streamline implementation and make key
rotation feasible by providing a centralized and trusted place where
MDLand community members can retrieve your public keys.
Registering a JWK Set URL
When building or licensing an application
that will authenticate against MDLand, you are prompted to optionally upload
JWK Set URL to your app. When registering this URL, be sure of the following: